﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using PowerTriggersWcf;
using System.Xml;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceProcess;
using System.Diagnostics;

namespace PowerTriggersWcf
{
    [DataContract(Namespace = "http://PWTWcf")]
    internal class TaskStartProcess : Task
    {
        #region constructors
        private TaskStartProcess()
            : base(TaskTypes.StartProcess, true)
        {
        }

        internal TaskStartProcess(bool setDefaultProperties)
            : base(TaskTypes.StartProcess, setDefaultProperties)
        {
        }

        internal TaskStartProcess(PWTProcess process)
            : this()
        {
            this.Description = process.Description;
            this.Sequence = 0;
            this.Status = string.Empty;
            this.ID = process.ExePath;
            this.Category = string.Empty;
        }

        internal TaskStartProcess(Process process)
            : this()
        {
            this.Description = ProcessLauncher.GetProcessDescription(process);
            this.Sequence = 0;
            this.Status = string.Empty;
            this.ID = ProcessLauncher.GetProcessExePath(process);
            this.Category = string.Empty;
        }
        #endregion

        internal override bool PerformAction(HistoryHeader historyHeader)
        {
            bool success;

            try
            {
                TaskPropertyChoice choice = GetSelectedChoice();
                switch (choice.ChoiceType)
                {
                    case TaskPropertyChoiceTypes.AllLoggedOnUsernames:
                        success = StartProcessForAllLoggedOnUsernames(historyHeader, this.ID);
                        break;
                    case TaskPropertyChoiceTypes.SpecifiedLoggedOnUsername:
                        success = StartProcessForSpecifiedLoggedOnUsername(historyHeader, this.ID, ((string[])choice.AdditionalData));
                        break;
                    default: // ServiceAccount
                        success = StartProcessForServiceAccount(historyHeader, this.ID);
                        break;
                }
            }
            catch (System.Exception ex)
            {
                string msg = string.Format("Error when attempting to start process '{0}'. Error={1}", this.Description, ex.Message.Trim());
                Service.AddHistory(historyHeader, HistorySeverityLevels.Error, msg, false);
                success = false;
            }

            return success;
        }

        private bool StartProcessForAllLoggedOnUsernames(HistoryHeader historyHeader, string filePath)
        {
            bool success = true;

            IDictionary<int, string> userNames = ProcessLauncher.GetLoggedOnSessions();

            foreach (var kvp in userNames)
            {
                int dwSessionId = kvp.Key;
                string username = kvp.Value;

                try
                {
                    if (!this.StartProcessForSessionId(historyHeader, filePath, dwSessionId, username))
                    {
                        success = false;
                    }
                }
                catch (System.Exception ex)
                {
                    string msg = string.Format("Error starting process '{0}' for user '{1}'. Error: {2}", this.Description, username ?? string.Empty, ex.Message.Trim());
                    Service.AddHistory(historyHeader, HistorySeverityLevels.Error, msg, false);
                    success = false;
                }
            }

            if (userNames.Count == 0)
            {
                string msg = string.Format("Process '{0}' was not started because no users were logged on.", this.Description);
                Service.AddHistory(historyHeader, HistorySeverityLevels.Informational, msg, false);
            }

            return success;
        }

        private bool StartProcessForSpecifiedLoggedOnUsername(HistoryHeader historyHeader, string filePath, string[] accountCredentials)
        {
            bool success = true;
            bool userFound = false;
            string specifiedUsername = Service.DecryptString(accountCredentials[0]);

            IDictionary<int, string> userNames = ProcessLauncher.GetLoggedOnSessions();

            foreach (var kvp in userNames)
            {
                int dwSessionId = kvp.Key;
                string username = kvp.Value;

                try
                {
                    if (username.ToUpper() == specifiedUsername.ToUpper())
                    {
                        userFound = true;
                        if (!this.StartProcessForSessionId(historyHeader, filePath, dwSessionId, username))
                        {
                            success = false;
                        }
                    }
                }
                catch (System.Exception ex)
                {
                    string msg = string.Format("Error starting process '{0}' for user '{1}'. Error: {2}", this.Description, username ?? string.Empty, ex.Message.Trim());
                    Service.AddHistory(historyHeader, HistorySeverityLevels.Error, msg, false);
                    success = false;
                }
            }

            if (!userFound)
            {
                string msg = string.Format("Process '{0}' was not started because user '{1}' was not logged on.", this.Description, specifiedUsername ?? string.Empty);
                Service.AddHistory(historyHeader, HistorySeverityLevels.Informational, msg, false);
            }

            return success;
        }

        private bool StartProcessForSessionId(HistoryHeader historyHeader, string filePath, int dwSessionId, string username)
        {
            bool success = true;
            filePath = Path.GetFullPath(filePath);

            try
            {
                ProcessLauncher.PROCESS_INFORMATION procInfo;
                ProcessLauncher.StartProcessAndBypassUAC(filePath, dwSessionId, false, out procInfo);

                var action = this.GetSelectedAction();

                switch (action.ChoiceType)
                {
                    case TaskPropertyChoiceTypes.WaitForExit:
                        try
                        {
                            using (Process process = Process.GetProcessById((int)procInfo.dwProcessId))
                            {
                                Service.SavePendingHistory();
                                if (!process.HasExited && !process.WaitForExit(60000))
                                {
                                    Service.AddHistory(historyHeader, HistorySeverityLevels.Warning, string.Format("Process '{0}' successfully started for user '{1}' although WaitForExit timed-out after 60 seconds.", this.Description, username ?? string.Empty), false);
                                }
                                else
                                {
                                    Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started for user '{1}'.", this.Description, username ?? string.Empty), false);
                                }
                            }
                        }
                        catch (System.Exception ex)
                        {
                            throw ex;
                        }
                        break;
                    case TaskPropertyChoiceTypes.WaitForIdle:
                        try
                        {
                            using (Process process = Process.GetProcessById((int)procInfo.dwProcessId))
                            {
                                Service.SavePendingHistory();
                                if (!process.HasExited && !process.WaitForInputIdle(60000))
                                {
                                    Service.AddHistory(historyHeader, HistorySeverityLevels.Warning, string.Format("Process '{0}' successfully started for user '{1}' although WaitForIdle timed-out after 60 seconds.", this.Description, username ?? string.Empty), false);
                                }
                                else
                                {
                                    Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started for user '{1}'", this.Description, username ?? string.Empty), false);
                                }
                            }
                        }
                        catch (System.Exception ex)
                        {
                            throw ex;
                        }
                        break;
                    case TaskPropertyChoiceTypes.Pause:
                        int? milliseconds = (int?)action.AdditionalData;
                        if (milliseconds.HasValue)
                        {
                            Service.SavePendingHistory();
                            System.Threading.Thread.Sleep(milliseconds.Value);
                        }
                        Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started for user '{1}'.", this.Description, username ?? string.Empty), false);
                        break;
                    default:
                        Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started for user '{1}'.", this.Description, username ?? string.Empty), false);
                        break;
                }
            }
            catch (System.Exception ex)
            {
                string msg = string.Format("Error starting process '{0}' for user '{1}'. Error: {2}", this.Description, username ?? string.Empty, ex.Message.Trim());
                Service.AddHistory(historyHeader, HistorySeverityLevels.Error, msg, false);
                success = false;
            }

            return success;
        }

        private bool StartProcessForServiceAccount(HistoryHeader historyHeader, string filePath)
        {
            var action = this.GetSelectedAction();

            using (Process process = new Process())
            {
                filePath = Path.GetFullPath(filePath);
                process.StartInfo = new ProcessStartInfo();
                process.StartInfo.FileName = "\"" + filePath + "\"";
                string directory = null;
                if (filePath.Contains(Path.DirectorySeparatorChar))
                {
                    directory = Path.GetDirectoryName(filePath);
                }
                if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory) && action.ChoiceType != TaskPropertyChoiceTypes.WaitForExit)
                {
                    process.StartInfo.WorkingDirectory = "\"" + directory + "\"";
                }
                else
                {
                    process.StartInfo.WorkingDirectory = string.Empty;
                }
                if (action.ChoiceType == TaskPropertyChoiceTypes.WaitForExit)
                {
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.RedirectStandardOutput = true;
                }
                else
                {
                    process.StartInfo.UseShellExecute = true;
                }
                process.Start();

                switch (action.ChoiceType)
                {
                    case TaskPropertyChoiceTypes.WaitForExit:
                        Service.SavePendingHistory();
                        string output = process.StandardOutput.ReadToEnd();
                        if (!process.HasExited && !process.WaitForExit(60000))
                        {
                            Service.AddHistory(historyHeader, HistorySeverityLevels.Warning, string.Format("Process '{0}' successfully started although WaitForExit timed-out after 60 seconds. Output from process: {1}", this.Description, output ?? string.Empty), false);
                        }
                        else
                        {
                            Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started. Output from process: {1}", this.Description, output ?? string.Empty), false);
                        }
                        break;
                    case TaskPropertyChoiceTypes.WaitForIdle:
                        Service.SavePendingHistory();
                        if (!process.HasExited && !process.WaitForInputIdle(60000))
                        {
                            Service.AddHistory(historyHeader, HistorySeverityLevels.Warning, string.Format("Process '{0}' successfully started although WaitForIdle timed-out after 60 seconds.", this.Description), false);
                        }
                        else
                        {
                            Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started", this.Description), false);
                        }
                        break;
                    case TaskPropertyChoiceTypes.Pause:
                        int? milliseconds = (int?)action.AdditionalData;
                        if (milliseconds.HasValue)
                        {
                            Service.SavePendingHistory();
                            System.Threading.Thread.Sleep(milliseconds.Value);
                        }
                        Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started.", this.Description), false);
                        break;
                    default:
                        Service.AddHistory(historyHeader, HistorySeverityLevels.Success, string.Format("Process '{0}' successfully started.", this.Description), false);
                        break;
                }
            }

            return true;
        }
    }
}
